package com.tcm.service.impl;

import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.*;
import com.tcm.entity.bo.ClozeItemExcel;
import com.tcm.entity.dto.ClozeItemUpdateDto;
import com.tcm.entity.dto.param.ClozeItemPageParam;
import com.tcm.entity.vo.BatchImportResult;
import com.tcm.entity.vo.ClozeItemListResult;
import com.tcm.entity.resp.PageR;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.*;
import com.tcm.service.*;
import com.tcm.utils.ExcelExportUtil;
import com.tcm.utils.ItemUtil;
import com.tcm.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 填空题业务实现类
 *
 * @author Guqier
 * @version 1.0
 * @date 2023/7/14 16:51
 */
@Slf4j
@Service
public class ClozeItemServiceImpl extends ServiceImpl<ClozeItemMapper, ClozeItem> implements ClozeItemService {

    @Autowired
    private BookService bookService;

    @Autowired
    private BookItemMapper bookItemMapper;

    @Autowired
    private ItemLabelMapper itemLabelMapper;

    @Autowired
    private LabelService labelService;

    @Autowired
    private ItemImageMapper itemImageMapper;

    public static final int CLOZE_TYPE = 1;// 填空题

    public static final int LABEL_PUBLIC_TYPE = 0;// 公共标签

    private static final int COURSE_TYPE = 0;// 课程类型

    private static final int NODE_TYPE = 1;// 书籍节点类型

    @Autowired
    private CodeService codeService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private TestPaperDetailMapper testPaperDetailMapper;

    @Lazy
    @Autowired
    private ItemStudentService itemStudentService;

    @Autowired
    private CollectService collectService;

    @Autowired
    private NoteService noteService;

    /**
     * 添加填空题
     *
     * @param clozeItemUpdateDto 填空题更新数据封装类
     * @param currUserId         当前用户ID
     * @return 添加结果，true为成功
     */
    @Override
    @Transactional
    public boolean addClozeItem(ClozeItemUpdateDto clozeItemUpdateDto, Long currUserId) {
        // 数据库操作结果，用于总体判断是否回滚
        boolean itemSave = false;// 题目保存
        int labelSave = 0;// 标签保存
        int imageSave = 0;// 配图保存
        int bookItemSave = 0;// 题目章节保存结果

        // 填空题实体类
        ClozeItem clozeItem = new ClozeItem();
        clozeItem.setDifficultyId(clozeItemUpdateDto.getDifficultyId());// 难度等级
        clozeItem.setContent(clozeItemUpdateDto.getContent());// 题目内容
        clozeItem.setMarkContent(clozeItemUpdateDto.getMarkContent());// 获取标记后的题目
        clozeItem.setAns(clozeItemUpdateDto.getAns());// 答案，使用|分割
        List<String> points = clozeItemUpdateDto.getKnowledgePoints();
        clozeItem.setKnowledgePoint(String.join("|", points));
        clozeItem.setCreateUser(currUserId);
        clozeItem.setUpdateUser(currUserId);
        itemSave = this.save(clozeItem);

        // 校验章节信息
        Book book = bookService.getById(clozeItemUpdateDto.getBookId());
        if (book == null) {
            return false;
        }
        if (!book.getType().equals(NODE_TYPE)) {
            throw new EntityInvalidException("题目只能所属于章节");
        }
        // 插入章节/题目关系
        BookItem bookItem = new BookItem();
        bookItem.setBookId(clozeItemUpdateDto.getBookId());
        bookItem.setType(CLOZE_TYPE);
        bookItem.setItemId(clozeItem.getId());
        bookItem.setCreateUser(currUserId);
        bookItem.setUpdateUser(currUserId);
        bookItemSave += bookItemMapper.insert(bookItem);

        // 添加标签
        LambdaQueryWrapper<Label> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelLambdaQueryWrapper.eq(Label::getType, LABEL_PUBLIC_TYPE);// 只查询公共标签
        List<Label> labels = labelService.list(labelLambdaQueryWrapper);
        for (String point : points) {// 遍历标签判断是否已存在，已存在则应引用
            Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
            Long labelId = null;
            if (!first.isPresent()) {// 如果标签不存在则创建一个
                Label label = new Label();
                label.setName(point);
                label.setType(LABEL_PUBLIC_TYPE);
                label.setCreateUser(currUserId);
                label.setUpdateUser(currUserId);
                if (!labelService.save(label)) {
                    break;// 保存失败直接跳过
                }
                labelId = label.getId();
            } else {// 已存在则直接引用已存在标签ID
                labelId = first.get().getId();
            }
            // 插入题目/标签关系表
            ItemLabel itemLabel = new ItemLabel();
            itemLabel.setItemId(clozeItem.getId());
            itemLabel.setLabelId(labelId);
            itemLabel.setType(LABEL_PUBLIC_TYPE);
            itemLabel.setCreateUser(currUserId);
            itemLabel.setUpdateUser(currUserId);
            labelSave += itemLabelMapper.insert(itemLabel);
        }
        // 插入图片关系
        List<Long> imageIds = clozeItemUpdateDto.getImageIds();
        if (!CollectionUtils.isEmpty(imageIds)) {
            for (Long imageId : imageIds) {
                ItemImage itemImage = new ItemImage();
                itemImage.setItemId(clozeItem.getId());
                itemImage.setImageId(imageId);
                itemImage.setCreateUser(currUserId);
                itemImage.setUpdateUser(currUserId);
                imageSave += itemImageMapper.insert(itemImage);
            }
            if (imageSave != imageIds.size()) {
                throw new DatabaseOperationException("保存图片数据到数据库异常");
            }
        }

        // 判断结果正确性
        if (!itemSave || labelSave != points.size() || bookItemSave != 1) {
            throw new DatabaseOperationException("数据库操作异常");
        }
        return true;
    }

    /**
     * 删除题目
     *
     * @param ids        题目ID列表
     * @param currUserId 当前登录用户ID
     * @return 删除结果，true为成功
     * @throws Exception 异常
     */
    @Override
    @Transactional
    public boolean removeClozeItem(List<Long> ids, Long currUserId) throws Exception {
        if (CollectionUtils.isEmpty(ids)) {
            return false;
        }
        // 校验处理结果
        int removeItem = 0;
        int removeBook = 0;
        int removeLabel = 0;
        int removeImage = 0;

        for (Long id : ids) {
            ClozeItem clozeItem = this.getById(id);
            if (clozeItem == null) {
                throw new EntityInvalidException("题目不存在");
            }
            // 判断是否被试卷引用
            if (testPaperDetailMapper.selectCountByItemId(clozeItem.getId()) != 0) {
                throw new EntityInvalidException(String.format("题目【%s】被试卷引用，无法删除", clozeItem.getContent()));
            }
            // 删除题目实体
            clozeItem.setUpdateUser(currUserId);
            if (this.updateById(clozeItem) && this.removeClozeItemById(id)) {
                removeItem++;
            }
            // 删除标签关系
            LambdaQueryWrapper<ItemLabel> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
            labelLambdaQueryWrapper.eq(ItemLabel::getItemId, id);
            Integer labelCount = itemLabelMapper.selectCount(labelLambdaQueryWrapper);
            if (itemLabelMapper.delete(labelLambdaQueryWrapper) == labelCount) {
                removeLabel++;
            }
            // 删除图片关系
            LambdaQueryWrapper<ItemImage> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
            imageLambdaQueryWrapper.eq(ItemImage::getItemId, id);
            Integer imageCount = itemImageMapper.selectCount(imageLambdaQueryWrapper);
            if (itemImageMapper.delete(imageLambdaQueryWrapper) == imageCount) {
                removeImage++;
            }
            // 删除章节关系
            LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
            bookItemLambdaQueryWrapper.eq(BookItem::getItemId, id);
            Integer count = bookItemMapper.selectCount(bookItemLambdaQueryWrapper);
            if (bookItemMapper.delete(bookItemLambdaQueryWrapper) == count) {
                removeBook++;
            }
            // 删除练习记录
            itemStudentService.remove(new LambdaQueryWrapper<ItemStudent>().eq(ItemStudent::getItemId, clozeItem.getId()));
            // 删除题目收藏
            collectService.remove(new LambdaQueryWrapper<Collect>().eq(Collect::getItemId, clozeItem.getId()));
            // 删除笔记
            noteService.remove(new LambdaQueryWrapper<Note>().eq(Note::getItemId, clozeItem.getId()));
        }
        if (removeItem != ids.size()) {
            throw new DatabaseOperationException("删除实体失败");
        }

        if (removeBook != ids.size()) {
            throw new DatabaseOperationException("删除章节关系失败");
        }

        if (removeLabel != ids.size()) {
            throw new DatabaseOperationException("删除标签关系失败");
        }

        if (removeImage != ids.size()) {
            throw new DatabaseOperationException("删除图片关系失败");
        }
        return true;
    }

    /**
     * 分页获取填空题列表
     *
     * @param param 分页参数
     * @return 填空题列表
     */
    @Override
    public PageR<ClozeItemListResult> getClozeItemListByPage(ClozeItemPageParam param) {
        Page<ClozeItem> page = new Page<>(param.getPage(), param.getPageSize());
        page.addOrder(OrderItem.desc("update_time"));

        // 查询符合条件的填空题
        LambdaQueryWrapper<ClozeItem> queryWrapper = this.buildClozeItemListQueryWrapper(param);
        this.page(page, queryWrapper);

        // 构造结果集
        PageR<ClozeItemListResult> pageR = new PageR<>();
        BeanUtils.copyProperties(page, pageR, "records");
        List<ClozeItemListResult> results = transferClozeItemToListResult(page.getRecords());
        pageR.setRecords(results);
        return pageR;
    }

    /**
     * 更新填空题信息
     *
     * @param clozeItemUpdateDto 填空题更新数据封装类
     * @param currUserId         当前登录用户ID
     * @return 处理结果，true为成功
     */
    @Override
    @Transactional
    @CacheEvict(value = AppProperties.ENTITY_CACHE_ITEM_CLOZE, key = "#clozeItemUpdateDto.itemId", condition = "#clozeItemUpdateDto.itemId != null")
    public boolean modifyClozeItem(ClozeItemUpdateDto clozeItemUpdateDto, Long currUserId) throws DatabaseOperationException {
        // 更新实体类信息
        ClozeItem clozeItem = this.getById(clozeItemUpdateDto.getItemId());
        if (clozeItem == null) {
            return false;
        }
        clozeItem.setDifficultyId(clozeItemUpdateDto.getDifficultyId());
        clozeItem.setKnowledgePoint(String.join("|", clozeItemUpdateDto.getKnowledgePoints()));
        clozeItem.setContent(clozeItemUpdateDto.getContent());
        clozeItem.setMarkContent(clozeItemUpdateDto.getMarkContent());
        clozeItem.setAns(clozeItemUpdateDto.getAns());
        clozeItem.setUpdateUser(currUserId);
        if (!this.updateById(clozeItem)) {
            throw new DatabaseOperationException("实体类更新失败");
        }

        // 校验章节信息
        Book book = bookService.getById(clozeItemUpdateDto.getBookId());
        if (book == null) {
            return false;
        }
        if (!book.getType().equals(NODE_TYPE)) {
            throw new EntityInvalidException("题目只能所属于章节");
        }
        // 更新题目/章节关联信息
        // 删除原有关系
        LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
        bookItemLambdaQueryWrapper.eq(BookItem::getItemId, clozeItem.getId());
        Integer count = bookItemMapper.selectCount(bookItemLambdaQueryWrapper);
        int deleteBookItem = bookItemMapper.delete(bookItemLambdaQueryWrapper);// 删除原有关系
        // 新增现有关系
        int bookItemUpdate = 0;
        BookItem bookItem = new BookItem();
        bookItem.setBookId(clozeItemUpdateDto.getBookId());
        bookItem.setItemId(clozeItem.getId());
        bookItem.setType(CLOZE_TYPE);
        bookItem.setCreateUser(currUserId);
        bookItem.setUpdateUser(currUserId);
        bookItemUpdate += bookItemMapper.insert(bookItem);

        if (bookItemUpdate != 1 || count != deleteBookItem) {
            throw new DatabaseOperationException("题目/章节关联关系更新失败");
        }

        // 更新图片信息
        // 删除原有关系
        int imageUpdate = 0;
        LambdaQueryWrapper<ItemImage> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        imageLambdaQueryWrapper.eq(ItemImage::getItemId, clozeItem.getId());
        Integer imageCount = itemImageMapper.selectCount(imageLambdaQueryWrapper);
        int imageDelete = itemImageMapper.delete(imageLambdaQueryWrapper);// 删除原有关系
        // 添加图片关系
        for (Long imageId : clozeItemUpdateDto.getImageIds()) {
            ItemImage itemImage = new ItemImage();
            itemImage.setItemId(clozeItem.getId());
            itemImage.setImageId(imageId);
            itemImage.setCreateUser(currUserId);
            itemImage.setUpdateUser(currUserId);
            imageUpdate += itemImageMapper.insert(itemImage);
        }
        if (imageUpdate != clozeItemUpdateDto.getImageIds().size() || imageDelete != imageCount) {
            throw new DatabaseOperationException("图片/题目关系更新失败");
        }

        // 更新标签关系
        // 删除原有标签关系
        LambdaQueryWrapper<ItemLabel> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelLambdaQueryWrapper.eq(ItemLabel::getItemId, clozeItem.getId());
        Integer labelCount = itemLabelMapper.selectCount(labelLambdaQueryWrapper);
        int labelDelete = itemLabelMapper.delete(labelLambdaQueryWrapper);
        // 添加标签题目关系
        int labelUpdate = 0;
        LambdaQueryWrapper<Label> labelsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelsLambdaQueryWrapper.eq(Label::getType, LABEL_PUBLIC_TYPE);// 只查询公共标签
        List<Label> labels = labelService.list(labelsLambdaQueryWrapper);
        for (String point : clozeItemUpdateDto.getKnowledgePoints()) {// 遍历标签判断是否已存在，已存在则应引用
            Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
            Long labelId = null;
            if (!first.isPresent()) {// 如果标签不存在则创建一个
                Label label = new Label();
                label.setName(point);
                label.setType(LABEL_PUBLIC_TYPE);
                label.setCreateUser(currUserId);
                label.setUpdateUser(currUserId);
                if (!labelService.save(label)) {
                    break;// 保存失败直接跳过
                }
                labelId = label.getId();
            } else {// 已存在则直接引用已存在标签ID
                labelId = first.get().getId();
            }
            // 插入题目/标签关系表
            ItemLabel itemLabel = new ItemLabel();
            itemLabel.setItemId(clozeItem.getId());
            itemLabel.setLabelId(labelId);
            itemLabel.setType(LABEL_PUBLIC_TYPE);
            itemLabel.setCreateUser(currUserId);
            itemLabel.setUpdateUser(currUserId);
            labelUpdate += itemLabelMapper.insert(itemLabel);
        }
        if (labelCount != labelDelete || labelUpdate != clozeItemUpdateDto.getKnowledgePoints().size()) {
            throw new DatabaseOperationException("标签更新失败");
        }
        return true;
    }

    /**
     * 批量导入填空题
     *
     * @param file       excel文件
     * @param currUserId 当前登录用户ID
     * @return 上传结果，true为成功
     * @throws IOException 异常
     */
    @Override
    public BatchImportResult<ClozeItemExcel> importClozeItem(MultipartFile file, Long currUserId) throws IOException {
        // 解析Excel文件，获取题目信息
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        reader.addHeaderAlias("课程", "course");
        reader.addHeaderAlias("难易级别", "difficulty");
        reader.addHeaderAlias("章节", "book");
        reader.addHeaderAlias("知识点", "knowledgePoints");
        reader.addHeaderAlias("考试题目", "content");
        List<ClozeItemExcel> clozeItemExcels = reader.readAll(ClozeItemExcel.class);
        // 获取书籍信息
        List<Book> books = bookService.getBookList();
        // 获取难易级别信息
        LambdaQueryWrapper<Code> codeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeLambdaQueryWrapper.eq(Code::getType, AppProperties.CODE_TYPE_DIFFICULTY);
        List<Code> difficultyList = codeService.list(codeLambdaQueryWrapper);
        // 获取标签列表
        List<Label> labels = labelService.list();
        // 构造结果集
        BatchImportResult<ClozeItemExcel> result = new BatchImportResult<>();// 响应、缓存结果
        List<ClozeItemExcel> successList = new ArrayList<>();// 校验通过列表
        List<ClozeItemExcel> failList = new ArrayList<>();// 校验失败列表
        List<ClozeItemUpdateDto> dtoList = new ArrayList<>();// 构造Dto列表，用于确认后进行保存操作

        // 校验导入题目信息
        for (ClozeItemExcel excel : clozeItemExcels) {
            // 校验课程
            Optional<Book> course = books.stream().filter(book -> book.getType() == COURSE_TYPE && book.getName().equals(excel.getCourse())).findFirst();
            if (!course.isPresent()) {
                excel.setMsg("课程不存在");
                failList.add(excel);
                continue;
            }
            // 校验书籍节点
            Optional<Book> node = books.stream().filter(book -> book.getType() == NODE_TYPE && book.getName().equals(excel.getBook())).findFirst();
            if (!node.isPresent()) {
                excel.setMsg("章节不存在");
                failList.add(excel);
                continue;
            }
            // 校验难度等级
            Optional<Code> difficulty = difficultyList.stream().filter(diff -> diff.getName().equals(excel.getDifficulty())).findFirst();
            if (!difficulty.isPresent()) {
                excel.setMsg("难易级别不存在");
                failList.add(excel);
                continue;
            }
            // 检测标签存在，不存在则添加
            String[] points = excel.getKnowledgePoints().split("\\|");
            boolean flag = true;// 标志标签是否有创建失败的
            for (String point : points) {// 遍历标签判断是否已存在
                Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
                if (!first.isPresent()) {// 如果标签不存在则创建一个
                    Label label = new Label();
                    label.setName(point);
                    label.setType(LABEL_PUBLIC_TYPE);
                    label.setCreateUser(currUserId);
                    label.setUpdateUser(currUserId);
                    if (!labelService.save(label)) {
                        flag = false;
                        break;// 保存失败直接跳过
                    }
                }
            }
            if (!flag) {
                excel.setMsg("标签创建失败");
                failList.add(excel);
                break;
            }

            // 通过校验，加入成功集
            excel.setMsg("校验通过");
            successList.add(excel);
            // 构造Dto
            ClozeItemUpdateDto dto = new ClozeItemUpdateDto();
            dto.setDifficultyId(difficulty.get().getId());// 难易级别，码表ID
            dto.setBookId(node.get().getId());// 所属章节ID
            dto.setKnowledgePoints(Arrays.asList(points));// 知识点归类
            dto.setContent(excel.getContent());// 题目
            dto.setAns(String.join("|", ItemUtil.analyzeClozeItemToAnswer(excel.getContent())));// 答案，使用|分割
            dto.setMarkContent(ItemUtil.analyzeClozeItemToMarkContent(excel.getContent()));// 标记后的题目
            dtoList.add(dto);
        }
        // 构造结果封装对象
        result.setUuid(UUID.randomUUID().toString());
        result.setTotal(clozeItemExcels.size());
        result.setSuccessCount(successList.size());
        result.setSuccessList(successList);
        result.setFailCount(failList.size());
        result.setFailList(failList);
        // 存入缓存，等待前端确认，超过5分钟自动清除
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_CLOZE_RES.getCacheKey(result.getUuid()), result, 5, TimeUnit.MINUTES);
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_CLOZE_DTO.getCacheKey(result.getUuid()), dtoList, 5, TimeUnit.MINUTES);
        log.info("填空题上传ID：{} 存入缓存", result.getUuid());
        return result;
    }

    /**
     * 确认导入填空题
     *
     * @param uuid       导入uuid
     * @param currUserId 当前登录用户ID
     * @return 导入结果，true为成功
     * @throws DatabaseOperationException 数据库操作异常
     */
    @Override
    @Transactional
    public boolean confirmBatchClozeItem(String uuid, Long currUserId) throws DatabaseOperationException {
        // 从缓存中取出数据
        List<ClozeItemUpdateDto> dtoList = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_CLOZE_DTO.getCacheKey(uuid));
        if (dtoList == null) {
            return false;
        }
        int cnt = 0;// 保存结果计数器
        for (ClozeItemUpdateDto dto : dtoList) {
            if (this.addClozeItem(dto, currUserId)) {
                cnt++;// 保存成功
            }
        }
        // 判断是否全部保存成功
        if (cnt != dtoList.size()) {
            throw new DatabaseOperationException("数据库保存失败");
        }
        // 保存成功则清除缓存
        redisUtil.deleteObject(CachePrefix.PC_IMPORT_CLOZE_DTO.getCacheKey(uuid));
        return true;
    }

    /**
     * 导出校验失败填空题信息
     *
     * @param uuid     导入uuid
     * @param response http响应对象
     * @throws Exception 异常
     */
    @Override
    public void exportImportFailedClozeItem(String uuid, HttpServletResponse response) throws Exception {
        // 从缓存中获取数据
        BatchImportResult<ClozeItemExcel> result = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_CLOZE_RES.getCacheKey(uuid));
        if (result == null) {
            throw new Exception("缓存不存在或已过期");
        }
        exportClozeItemCommon(result.getFailList(), response);
    }

    /**
     * 通用填空题导出
     *
     * @param excels   填空题导出封装对象列表
     * @param response http响应对象
     * @throws IOException 异常
     */
    public void exportClozeItemCommon(List<ClozeItemExcel> excels, HttpServletResponse response) throws IOException {
        // 设置导出列名
        ExcelWriter writer = ExcelUtil.getWriter();
        writer.addHeaderAlias("course", "课程");
        writer.addHeaderAlias("difficulty", "难易级别");
        writer.addHeaderAlias("book", "章节");
        writer.addHeaderAlias("knowledgePoints", "知识点");
        writer.addHeaderAlias("content", "考试题目");
        writer.addHeaderAlias("msg", "导出/导出状态");

        // 响应数据
        writer.write(excels, true);

        // 导出数据
        ExcelExportUtil.commonExport(writer, response);
    }

    /**
     * 导出填空题信息
     *
     * @param param    条件参数封装
     * @param response http响应对象
     * @throws IOException 异常
     */
    @Override
    public void exportClozeItem(ClozeItemPageParam param, HttpServletResponse response) throws IOException {
        // 获取书籍列表
        List<Book> books = bookService.getBookList();
        // 获取难易级别列表
        LambdaQueryWrapper<Code> codeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeLambdaQueryWrapper.eq(Code::getType, AppProperties.CODE_TYPE_DIFFICULTY);
        List<Code> difficultyList = codeService.list(codeLambdaQueryWrapper);

        // 查询符合条件的题目
        LambdaQueryWrapper<ClozeItem> queryWrapper = buildClozeItemListQueryWrapper(param);
        List<ClozeItem> clozeItems = this.list(queryWrapper);

        // 获取题目章节关系
        List<Long> ids = clozeItems.stream().map(ClozeItem::getId).collect(Collectors.toList());
        List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .eq(BookItem::getType, AppProperties.ITEM_TYPE_CLOZE)
                .in(!CollectionUtils.isEmpty(ids), BookItem::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        // 构造结果集
        List<ClozeItemExcel> excels = new ArrayList<>();
        for (ClozeItem clozeItem : clozeItems) {
            ClozeItemExcel excel = new ClozeItemExcel();
            // 查询课程分类、章节
            Optional<BookItem> bookItemOptional = bookItems.stream().filter(bookItem -> bookItem.getItemId().equals(clozeItem.getId())).findFirst();
            if (bookItemOptional.isPresent()) {
                Book course = bookService.getCourseByBookNodeId(bookItemOptional.get().getBookId(), books);
                excel.setCourse(course.getName());
                Optional<Book> optional = books.stream().filter(book -> book.getId().equals(bookItemOptional.get().getBookId())).findFirst();
                optional.ifPresent(book -> excel.setBook(book.getName()));
            }
            // 查询难易级别
            difficultyList.stream().filter(code -> code.getId().equals(clozeItem.getDifficultyId())).findFirst().ifPresent(code -> {
                excel.setDifficulty(code.getName());
            });
            // 构造剩余部分
            excel.setKnowledgePoints(clozeItem.getKnowledgePoint());
            excel.setContent(clozeItem.getContent());
            excel.setMsg("正常导出");
            excels.add(excel);
        }
        // 响应导出文件
        exportClozeItemCommon(excels, response);
    }

    /**
     * 通过ClozeItemPageParam构造queryWrapper
     *
     * @param param ClozeItemPageParam参数封装
     * @return queryWrapper
     */
    private LambdaQueryWrapper<ClozeItem> buildClozeItemListQueryWrapper(ClozeItemPageParam param) {
        LambdaQueryWrapper<ClozeItem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(param.getContent() != null, ClozeItem::getContent, param.getContent());// 题目内容模糊查询
        queryWrapper.eq(param.getDifficultyId() != null, ClozeItem::getDifficultyId, param.getDifficultyId());// 难易程度
        // 如果有书籍章节ID
        if (param.getBookId() != null) {
            // 查询书籍章节关联的题目
            // 获取指定书籍节点及其子节点
            List<Book> books = bookService.getBookList();
            List<Long> bookIds = bookService.findChildNode(param.getBookId(), books).stream().map(Book::getId).collect(Collectors.toList());
            bookIds.add(param.getBookId());// 加入本
            LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
            bookItemLambdaQueryWrapper.in(BookItem::getBookId, bookIds);
            List<BookItem> bookItems = bookItemMapper.selectList(bookItemLambdaQueryWrapper);
            List<Long> itemIds = bookItems.stream().map(BookItem::getItemId).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(itemIds)) {
                queryWrapper.in(ClozeItem::getId, itemIds);
            } else {
                queryWrapper.apply("1 != 1");// 如果题目ID为空则查询为空
            }
        }
        // 如果有标签
        if (!CollectionUtils.isEmpty(param.getLabels())) {
            // 获取符合条件的题目ID
            List<Long> ids = new ArrayList<>();
            for (String s : param.getLabels()) {
                ids.addAll(itemLabelMapper.selectItemIdByLabelName(s));
            }
            if (!CollectionUtils.isEmpty(ids)) {
                queryWrapper.in(ClozeItem::getId, ids);
            } else {
                queryWrapper.apply("1 != 1");// 如果题目ID为空则查询为空
            }
        }
        return queryWrapper;
    }

    /**
     * 根据ID列表查询填空题
     *
     * @param ids 填空题ID列表
     * @return 填空题列表展示对象集合
     */
    @Override
    public List<ClozeItemListResult> getClozeItemByIds(List<Long> ids) {
        // 查询所需书籍
        List<ClozeItem> clozeItems = this.listByIds(ids);
        // 转换为列表展示对象
        return transferClozeItemToListResult(clozeItems);
    }

    /**
     * 将填空题对象转换为列表展示对象
     *
     * @param clozeItems 填空题对象列表
     * @return 列表展示对象
     */
    @Override
    public List<ClozeItemListResult> transferClozeItemToListResult(List<ClozeItem> clozeItems) {
        List<Long> ids = clozeItems.stream().map(ClozeItem::getId).collect(Collectors.toList());
        // 准备数据
        List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .in(!CollectionUtils.isEmpty(ids), BookItem::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        List<Book> books = bookService.getBookList();
        List<ItemImage> itemImages = itemImageMapper.selectList(new LambdaQueryWrapper<ItemImage>()
                .in(!CollectionUtils.isEmpty(ids), ItemImage::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        // 转换数据
        return clozeItems.stream().map(clozeItem -> {
            ClozeItemListResult clozeItemListResult = new ClozeItemListResult();
            BeanUtils.copyProperties(clozeItem, clozeItemListResult, "knowledgePoint");
            clozeItemListResult.setKnowledgePoints(Arrays.asList(clozeItem.getKnowledgePoint().split("\\|")));
            clozeItemListResult.setAns(Arrays.asList(clozeItem.getAns().split("\\|")));
            // 查询所属章节
            clozeItemListResult.setBooks(bookService.getBooksOfItem(clozeItem.getId(), bookItems, books));
            // 查询配图
            clozeItemListResult.setImageIds(itemImages
                    .stream()
                    .filter(itemImage -> itemImage.getItemId().equals(clozeItem.getId()))
                    .map(ItemImage::getImageId)
                    .collect(Collectors.toList()));
            return clozeItemListResult;
        }).collect(Collectors.toList());
    }

    /**
     * 根据ID获取填空题(缓存)
     *
     * @param id 填空题ID
     * @return 填空题
     */
    @Override
    @Cacheable(value = AppProperties.ENTITY_CACHE_ITEM_CLOZE, key = "#id", unless = "#result == null")
    public ClozeItem getClozeItemById(Long id) {
        return getById(id);
    }

    /**
     * 根据ID删除填空题（缓存）
     *
     * @param id 填空题ID
     * @return 删除结果
     */
    @Override
    @CacheEvict(value = AppProperties.ENTITY_CACHE_ITEM_CLOZE, key = "#id", condition = "#id != null")
    public boolean removeClozeItemById(Long id) {
        return this.removeById(id);
    }

}
